﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BankOCR
{
    public class Parser
    {
        private ITokenizer _tokenizer;
        private IHashVerifier _hashVerifier;
        private ISymbolFixer _symbolFixer;

        public Parser(ITokenizer tokenizer = null, IHashVerifier hashVerifier = null, ISymbolFixer symbolFixer = null)
        {
            _tokenizer = tokenizer ?? new Tokenizer();
            _hashVerifier = hashVerifier ?? new HashVerifier();
            _symbolFixer = symbolFixer ?? new SingleChangeSymbolFixer();
        }

        private Dictionary<DigitalSymbol, char> _symbolValues = new Dictionary<DigitalSymbol, char> {
            {DigitalSymbol.Zero, '0'}, {DigitalSymbol.One, '1'}, {DigitalSymbol.Two, '2'}, {DigitalSymbol.Three, '3'},
            {DigitalSymbol.Four, '4'}, {DigitalSymbol.Five, '5'}, {DigitalSymbol.Six, '6'}, {DigitalSymbol.Seven, '7'},
            {DigitalSymbol.Eight, '8'}, {DigitalSymbol.Nine, '9'}
        };

        public IEnumerable<string> Parse(IEnumerable<string> inputLines)
        {
            var index = 0;
            var lineQuads = inputLines.GroupBy(line => (int)(index++ / 4));
            return lineQuads.Select(quad => ParseAccountNumber(quad.ElementAt(0), quad.ElementAt(1), quad.ElementAt(2))).ToArray();
        }

        private string ParseAccountNumber(string topLine, string middleLine, string bottomLine)
        {
            var symbols = _tokenizer.TokenizeLineOfSymbols(topLine, middleLine, bottomLine);
            var accountNumber = Stringify(symbols);
            return IsValid(accountNumber) ? accountNumber : TryToFix(symbols.ToArray());
        }

        private string Stringify(IEnumerable<DigitalSymbol> symbols)
        {
            var symbolValues = symbols.Select(s => _symbolValues.ContainsKey(s) ? _symbolValues[s] : '?').ToArray();
            return new string(symbolValues);
        }

        private bool IsValid(string accountNumber)
        {
            return !accountNumber.Contains('?') && _hashVerifier.IsHashCorrect(accountNumber);
        }

        private string TryToFix(DigitalSymbol[] symbols)
        {
            var validSymbolsFound = new List<string>();

            for (var i = 0; i < symbols.Length; i++) {
                foreach (var altSymbol in _symbolFixer.FindValidSymbolsFor(symbols[i])) {
                    var newSymbols = (DigitalSymbol[])symbols.Clone();
                    newSymbols[i] = altSymbol;
                    var newNumber = Stringify(newSymbols);
                    if (IsValid(newNumber))
                        validSymbolsFound.Add(newNumber);
                }
            }

            return ComposeFinalLine(Stringify(symbols), validSymbolsFound);
        }

        private string ComposeFinalLine(string originalNumber, IEnumerable<string> foundPossibilities)
        {
            if (foundPossibilities.Count() == 0)
                return originalNumber + (originalNumber.Contains('?') ? " ILL" : " ERR");

            if (foundPossibilities.Count() > 1)
                return originalNumber + " AMB [" + string.Join(", ", foundPossibilities) + "]";

            return foundPossibilities.Single();
        }
    }
}
